﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

public static class StreamAsyncEx
{
#pragma warning disable 1591

    private static byte[] NewLineBytes = Encoding.ASCII.GetBytes(Environment.NewLine);

    public static int DefaultBufferSize { get; set; } = 81920;
    public const int MinimumBufferSize = 4096;

    public static async Task<int> EatAllAsync(this Stream source, int bufferSize, CancellationToken cancellationToken, bool close = false)
    {
        try
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (bufferSize < MinimumBufferSize)
                bufferSize = MinimumBufferSize;

            var count = 0;
            int read;
            var buffer = new byte[bufferSize];

            while ((read = await source.ReadAsync(buffer, 0, bufferSize, cancellationToken)) > 0)
                count += read;
            return count;
        }
        finally
        {
            if (close) source.Close();
        }
    }
    public static Task<int> EatAllAsync(this Stream source, CancellationToken cancellationToken, bool close = false)
        => source.EatAllAsync(DefaultBufferSize, cancellationToken, close);
    public static Task<int> EatAllAsync(this Stream source, int bufferSize, bool close = false)
        => source.EatAllAsync(bufferSize, CancellationToken.None, close);
    public static Task<int> EatAllAsync(this Stream source, bool close = false)
        => source.EatAllAsync(DefaultBufferSize, CancellationToken.None, close);

    public static async Task<long> FeedToAsync(this Stream source, Stream target, int bufferSize, CancellationToken cancellationToken, bool closeSource, bool closeTarget)
    {
        try
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (bufferSize < MinimumBufferSize)
                bufferSize = MinimumBufferSize;

            long count = 0;
            int read;
            var buffer = new byte[bufferSize];

            while ((read = await source.ReadAsync(buffer, 0, bufferSize)) > 0)
            {
                await target.WriteAsync(buffer, 0, read);
                count += read;
            }
            return count;
        }
        finally
        {
            if (closeSource) source.Close();
            if (closeTarget) target.Close();
        }
    }
    public static Task<long> FeedToAsync(this Stream source, Stream target, int bufferSize, CancellationToken cancellationToken, bool close = false)
        => source.FeedToAsync(target, bufferSize, cancellationToken, close, close);
    public static Task<long> FeedToAsync(this Stream source, Stream target, CancellationToken cancellationToken, bool closeSource, bool closeTarget)
        => source.FeedToAsync(target, DefaultBufferSize, cancellationToken, closeSource, closeTarget);
    public static Task<long> FeedToAsync(this Stream source, Stream target, CancellationToken cancellationToken, bool close = false)
        => source.FeedToAsync(target, DefaultBufferSize, cancellationToken, close, close);
    public static Task<long> FeedToAsync(this Stream source, Stream target, int bufferSize, bool closeSource, bool closeTarget)
        => source.FeedToAsync(target, bufferSize, CancellationToken.None, closeSource, closeTarget);
    public static Task<long> FeedToAsync(this Stream source, Stream target, int bufferSize, bool close = false)
        => source.FeedToAsync(target, bufferSize, CancellationToken.None, close, close);
    public static Task<long> FeedToAsync(this Stream source, Stream target, bool closeSource, bool closeTarget)
        => source.FeedToAsync(target, DefaultBufferSize, CancellationToken.None, closeSource, closeTarget);
    public static Task<long> FeedToAsync(this Stream source, Stream target, bool close = false)
        => source.FeedToAsync(target, DefaultBufferSize, CancellationToken.None, close, close);

    public static async Task<int> ReadBlockAsync(this Stream source, byte[] buffer, int offset, int count, CancellationToken cancellationToken)
    {
        var len = buffer.Length;
        if (offset < 0 || offset > len)
            throw new ArgumentException(null, nameof(offset));
        if (count < 0 || count > len)
            throw new ArgumentException(null, nameof(count));
        if (count == 0)
            return 0;
        if (offset + count > len)
            throw new ArgumentException();

        var read = 0;
        do
        {
            var a = await source.ReadAsync(buffer, offset + read, count - read, cancellationToken);
            if (a == 0)
                break;

            read += a;
        } while (read < count);

        return read;
    }
    public static Task<int> ReadBlockAsync(this Stream source, byte[] buffer, int offset, int count)
        => source.ReadBlockAsync(buffer, offset, count, CancellationToken.None);

    public static Task WriteLineAsync(Stream stream, CancellationToken token)
    {
        return stream.WriteAsync(NewLineBytes, 0, NewLineBytes.Length, token);
    }
}
